//-----------------------------------------------------------------------
// <copyright file="TuplePool.cs" company="tuliprules.org">
//     Copyright (c) 2010 tuplelister, tuliprules.org.
// </copyright>
// <summary>This is the TuplePool class.</summary>
//-----------------------------------------------------------------------

// #define NOPOOL
namespace Tulip.Engine.Tuples
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;

    /// <summary>
    /// Maintains a pool of available Tuples.   The pool is partitioned by Tuple type.
    /// </summary>
    /// <remarks>
    /// <para>Pooling Tuples is a significant optimisation where a large number of facts are
    /// handled by the engine, and where there is a lot of retraction and reassertion of Facts.
    /// By pooling and recycling Tuple instances, garbage collection of Tuples during execution of
    /// a ruleset can be eliminated, and the cost of instantiation can be  reduced.   Miss Manners
    /// benchmarking showed an approx 20% improvement in performance with poolicg switched on.</para>
    /// <para>The TuplePool is a global singleton that acts as a class factory and instance recycler.</para>
    /// </remarks>
    public static class TuplePool
    {
        /// <summary>
        /// Stores pooled tuples.   The repository is partitioned by tuple type.
        /// </summary>
        private static readonly Dictionary<Type, PooledTupleList> TupleRepository = new Dictionary<Type, PooledTupleList>();

        /// <summary>
        /// The current pooled tuple list.
        /// </summary>
        private static PooledTupleList currentTupleList;

        /// <summary>
        /// Indicates if the type of the tuples in the pool is currently fixed.
        /// </summary>
        private static bool isTypeFixed;

        /// <summary>
        /// Fixes the pool to return a given tuple list type.
        /// </summary>
        /// <param name="tupleType">The type of tuple to which the pool is fixed.</param>
        public static void FixType(Type tupleType)
        {
            //////currentTupleClassGet = tupleClass;
            currentTupleList = TupleRepository[tupleType];
            isTypeFixed = true;
        }

        /// <summary>
        /// Un-fixes a currently fixed tupe do that the pool can return any tuple list type.
        /// </summary>
        public static void UnfixType()
        {
            //////currentTupleClassGet = null;
            currentTupleList = null;
            isTypeFixed = false;
        }

        /// <summary>
        /// Registers a tuple type with the pool.
        /// </summary>
        /// <param name="tupleType">The type of tuple to register with the pool.</param>
        public static void RegisterTupleType(Type tupleType)
        {
            // Add list to repository
            if (!TupleRepository.ContainsKey(tupleType))
            {
                TupleRepository.Add(tupleType, new PooledTupleList());
            }
        }

        /// <summary>
        /// Gets a Tuple.   This may be a recycled Tuple from the pool, or, if the
        /// pool is empty, a new Tuple.
        /// </summary>
        /// <typeparam name="TTuple">The tuple type.</typeparam>
        /// <returns>
        /// A Tuple link instance.
        /// </returns>
        public static TTuple GetItem<TTuple>() where TTuple : Tuple
        {
#if NOPOOL
            T recycledTuple = default(T);
            recycledTuple = (Tuple)Activator.CreateInstance(typeof(T));
            recycledTuple.IsPooled = false;
            return recycledTuple;
#else
            // TODO: Consider using a weak reference to allow garbage collection.
            var tupleList = isTypeFixed ? currentTupleList : TupleRepository[typeof(TTuple)];
            var recycledTuple = tupleList.Count == 0
                                       ? (TTuple)Activator.CreateInstance(typeof(TTuple))
                                       : (TTuple)tupleList.Remove();
            recycledTuple.IsPooled = false;

            return recycledTuple;
#endif
        }

        /// <summary>
        /// Releases a Tuple back to the pool.
        /// </summary>
        /// <param name="returnedTuple">The tuple to be returned to the pool.</param>
        public static void ReturnItem(Tuple returnedTuple)
        {
#if NOPOOL
            returnedTuple.Clear();
#else
            Debug.Assert((returnedTuple != null), "The returned Tuple cannot be null");

            if (returnedTuple.IsPooled)
            {
                return;
            }

            returnedTuple.Clear();

            // When getting an item, we introduce a small optimisation that avoids indexing 
            // into the tuple repository if the type of tuple we require is the same as the 
            // previous tuple we got.   However, we avoid this optimisation here because of
            // the recursive fashion in which subsumer lists are traversed when retracting 
            // facts.   In this case, it is less likely that the next returned tuple will 
            // be of the same type, and testing appears to suggest that it is slightly more 
            // optimal to always index into the tuple repository.
            TupleRepository[returnedTuple.GetType()].Add(returnedTuple);
            returnedTuple.IsPooled = true;
#endif
        }

        /// <summary>
        /// Clears all items from the Tuple  pool.
        /// </summary>
        public static void Clear()
        {
#if !NOPOOL
            TupleRepository.Clear();
#endif
        }

        /// <summary>
        /// Gets a Tuple.   This may be a recycled Tuple from the pool, or, if the
        /// pool is empty, a new Tuple.
        /// </summary>
        /// <param name="tupleClass">
        /// The required fact type. 
        /// </param>
        /// <returns>
        /// A Tuple link instance.
        /// </returns>
        internal static Tuple GetItem(Type tupleClass)
        {
#if NOPOOL
            Tuple recycledTuple = null;
            recycledTuple = (Tuple)Activator.CreateInstance(tupleClass);
            recycledTuple.IsPooled = false;
            return recycledTuple;
#else
            // TODO: Consider using a weak reference to allow garbage collection.
            var tupleList = isTypeFixed ? currentTupleList : TupleRepository[tupleClass];
            var recycledTuple = tupleList.Count == 0 ? (Tuple)Activator.CreateInstance(tupleClass) : tupleList.Remove();
            recycledTuple.IsPooled = false;

            return recycledTuple;
#endif
        }

        /// <summary>
        /// A list of pooled tuples.
        /// </summary>
        private class PooledTupleList
        {
            /// <summary>
            /// Count of tuples in the pool.
            /// </summary>
            public int Count;

            /// <summary>
            /// Head tuple in the pooled tuple list.
            /// </summary>
            private Tuple head;

            /// <summary>
            /// Adds a tupel to the pool.
            /// </summary>
            /// <param name="tuple">The tuple to be added.</param>
            public void Add(Tuple tuple)
            {
                var currentHead = this.head;

                this.head = tuple;

                if (currentHead != null)
                {
                    this.head.PoolPrevious = currentHead;
                }

                this.Count++;
            }

            /// <summary>
            /// Removes and returns a tuple for the pool.
            /// </summary>
            /// <returns>The removed tuple.</returns>
            public Tuple Remove()
            {
                var currentHead = this.head;

                if (currentHead != null)
                {
                    this.head = currentHead.PoolPrevious;
                    this.Count--;
                }

                return currentHead;
            }
        }
    }
}
